// Copyright (c) 2012, the Riak-Dart project authors (see AUTHORS file).
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/** Riak Client */
interface Client {

  /** Fetch the specified riak object */
  Future<Fetched> fetch(ID target, [bool stream]);

  /** Store the specified riak object */
  Future<Stored> store(ID target, Content content);

  /** Delete the specified riak object */
  Future<Deleted> delete(ID target);

  /** Query the index, iterate the keys, call the handler and summarize the results. */
  Future<Queried> indexQuery(IndexQuery query, [Future<Dynamic> callback(String key)]);

  /** clean up the client and close any resources it might be using */
  void close();
}

/** Riak ID metadata (with conditional request/update options) */
class ID {

  /** bucket name */
  String bucket;

  /** key */
  String key;

  /** as HTTP ETag header */
  String etag;

  /** as Riak's vclock */
  String vclock;

  /** last modified */
  Date modified;

  /** Default constructor */
  ID(this.bucket, this.key, [this.etag, this.vclock, this.modified]);

  /** To ensure there is no entry on store, we set the modified date 30 hours before the actual time */
  ID.notExists(this.bucket, this.key, [int hours = 30]) {
    notModifiedSince(hours);
  }

  void notModifiedSince([int hours = 30]) {
    this.modified = new Date.now().subtract(new Duration(0, hours, 0, 0, 0));
  }
}

/** Content defintion */
class Content {

  static final String JSON = "application/json";
  static final String PLAIN = "text/plain";
  static final String HTML = "text/html";

  /** object's media/content type */
  String mediaType;

  /** content value: String, List<int> or InputStream */
  var value;

  /** Secondary index data */
  Index _index;
  Index get index() { if (_index == null) _index = new Index(); return _index; }

  /* String content */
  Content.json(String text) : mediaType = JSON, value = text;
  Content.text(String text, [this.mediaType = PLAIN]) : value = text;
  Content.html(String text) : mediaType = HTML, value = text;

  /* Binary content */
  Content.binary(this.mediaType, List<int> data) : value = data;

  /* Stream content */
  Content.stream(this.mediaType, io.InputStream input) : value = input;

  bool hasIndex() { return _index != null; }
}

/** Secondary index data */
class Index {

  Map<String, List<Dynamic>> _map;
  Index() {_map = new LinkedHashMap();}

  void stringValue(String name, String value) { _addValue("${name}_bin", value); }
  void intValue(String name, int value) { _addValue("${name}_int", value); }

  void shortDate(String name, Date date) {
    StringBuffer sb = _startDate(date);
    _addValue("${name}_bin", sb.toString());
  }

  void longDate(String name, Date date) {
    StringBuffer sb = _startDate(date);
    sb.add("-");
    if (date.hours < 10) sb.add("0");
    sb.add(date.hours);
    if (date.minutes < 10) sb.add("0");
    sb.add(date.minutes);
    if (date.seconds < 10) sb.add("0");
    sb.add(date.seconds);
    _addValue("${name}_bin", sb.toString());
  }

  StringBuffer _startDate(Date date) {
    StringBuffer sb = new StringBuffer();
    sb.add(date.year.toString());
    if (date.month < 10) sb.add("0");
    sb.add(date.month.toString());
    if (date.day < 10) sb.add("0");
    sb.add(date.day);
    return sb;
  }
  _addValue(String key, Object value) {
    List<Object> list = _map[key];
    if (list == null) {
      _map[key] = [ value ];
    } else {
      list.add(value);
    }
  }

  Collection<String> getKeys() { return _map.getKeys(); }
  List<Dynamic> getIndexValues(String key) { return _map[key]; }
}

/** Fetch response object */
class Fetched {
  bool success;
  int statusCode;
  ID header;
  Content content;
}

/** Store response object */
class Stored {
  bool success;
  int responseCode;
}

/** Delete response object */
class Deleted {
  bool success;
  int responseCode;
}

/** QueryIndex request object */
class IndexQuery {
  String bucket;
  String index;
  var start;
  var end;

  IndexQuery.textRange(this.bucket, String indexName, String this.start, String this.end) : this.index = "${indexName}_bin";
  IndexQuery.textValue(this.bucket, String indexName, String value) : this.index = "${indexName}_bin", this.start = value;
  IndexQuery.intRange(this.bucket, String indexName, int this.start, int this.end) : this.index = "${indexName}_int";
  IndexQuery.intValue(this.bucket, String indexName, int value) : this.index = "${indexName}_int", this.start = value;
}

/** Query(Index) response object */
class Queried {
  bool success;
  int responseCode;
  int counter;
  List<String> keys;
}
